One place for hosting & domains

      How To Share State Across React Components with Context


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In this tutorial, you’ll share state across multiple components using React context. React context is an interface for sharing information with other components without explicitly passing the data as props. This means that you can share information between a parent component and a deeply nested child component, or store site-wide data in a single place and access them anywhere in the application. You can even update data from nested components by providing update functions along with the data.

      React context is flexible enough to use as a centralized state management system for your project, or you can scope it to smaller sections of your application. With context, you can share data across the application without any additional third-party tools and with a small amount of configuration. This provides a lighter weight alternative to tools like Redux, which can help with larger applications but may require too much setup for medium-sized projects.

      Throughout this tutorial, you’ll use context to build an application that use common data sets across different components. To illustrate this, you’ll create a website where users can build custom salads. The website will use context to store customer information, favorite items, and custom salads. You’ll then access that data and update it throughout the application without passing the data via props. By the end of this tutorial, you’ll learn how to use context to store data at different levels of the project and how to access and update the data in nested components.

      Prerequisites

      Step 1 — Building the Basis for Your Application

      In this step, you’ll build the general structure of your custom salad builder. You’ll create components to display possible toppings, a list of selected toppings, and customer information. As you build the application with static data, you’ll find how different pieces of information are used in a variety of components and how to identify pieces of data that would be helpful in a context.

      Here’s an example of the application you will build:

      Salad Builder Site

      Notice how there is information that you might need to use across components. For example, the username (which for this sample is Kwame) displays user data in a navigation area, but you may also need user information to identify favorite items or for a checkout page. The user information will need to be accessible by any component in the application. Looking at the salad builder itself, each salad ingredient will need to be able to update the Your Salad list at the bottom of the screen, so you’ll need to store and update that data from a location that is accessible to each component as well.

      Start by hard-coding all the data so that you can work out the structure of your app. Later, you’ll add in the context starting in the next step. Context provides the most value as applications start to grow, so in this step you’ll build several components to show how context works across a component tree. For smaller components or libraries, you can often use wrapping components and lower level state management techniques, like React Hooks and class-based management.

      Since you are building a small app with multiple components, install JSS to make sure there won’t be any class name conflicts and so that you can add styles in the same file as a component. For more on JSS, see Styling React Components.

      Run the following command:

      npm will install the component, and when it completes you’ll see a message like this:

      Output

      + react-jss@10.3.0 added 27 packages from 10 contributors, removed 10 packages andaudited 1973 packages in 15.507s

      Now that you have JSS installed, consider the different components you’ll need. At the top of the page, you’ll have a Navigation component to store the welcome message. The next component will be the SaladMaker itself. This will hold the title along with the builder and the Your Salad list at the bottom. The section with ingredients will be a separate component called the SaladBuilder, nested inside SaladMaker. Each ingredient will be an instance of a SaladItem component. Finally, the bottom list will be a component called SaladSummary.

      Note: The components do not need to be divided this way. As you work on your applications, your structure will change and evolve as you add more functionality. This example is meant to give you a structure to explore how context affects different components in the tree.

      Now that you have an idea of the components you’ll need, make a directory for each one:

      • mkdir src/components/Navigation
      • mkdir src/components/SaladMaker
      • mkdir src/components/SaladItem
      • mkdir src/components/SaladBuilder
      • mkdir src/components/SaladSummary

      Next, build the components from the top down starting with Navigation. First, open the component file in a text editor:

      • nano src/components/Navigation/Navigation.js

      Create a component called Navigation and add some styling to give the Navigation a border and padding:

      [state-context-tutorial/src/components/Navigation/Navigation.js]
      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          borderBottom: 'black solid 1px',
          padding: [15, 10],
          textAlign: 'right',
        }
      });
      
      export default function Navigation() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            Welcome, Kwame
          </div>
        )
      }
      

      Since you are using JSS, you can create style objects directly in the component rather than a CSS file. The wrapper div will have a padding, a solid black border, and align the text to the right with textAlign.

      Save and close the file. Next, open App.js, which is the root of the project:

      • nano src/components/App/App.js

      Import the Navigation component and render it inside empty tags by adding the highlighted lines:

      state-context-tutorial/src/components/App/App.js

      import React from 'react';
      import Navigation from '../Navigation/Navigation';
      
      function App() {
        return (
          <>
            <Navigation />
          </>
        );
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will refresh and you’ll see the navigation bar:

      Navigation Bar

      Think of the navigation bar as a global component, since in this example it’s serving as a template component that will be reused on every page.

      The next component will be the SaladMaker itself. This is a component that will only render on certain pages or in certain states.

      Open SaladMaker.js in your text editor:

      • nano src/components/SaladMaker/SaladMaker.js

      Create a component that has an <h1> tag with the heading:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
          </>
        )
      }
      

      In this code, you are using textAlign to center the component on the page. The role and aria-label attributes of the span element will help with accessibility using Accessible Rich Internet Applications (ARIA).

      Save and close the file. Open App.js to render the component:

      • nano src/components/App/App.js

      Import SaladMaker and render after the Navigation component:

      state-context-tutorial/src/components/App/App.js

      import React from 'react';
      import Navigation from '../Navigation/Navigation';
      import SaladMaker from '../SaladMaker/SaladMaker';
      
      function App() {
        return (
          <>
            <Navigation />
            <SaladMaker />
          </>
        );
      }
      
      export default App;
      

      Save and close the file. When you do, the page will reload and you’ll see the heading:

      Salad Maker Page

      Next, create a component called SaladItem. This will be a card for each individual ingredient.

      Open the file in your text editor:

      • nano src/components/SaladItem/SaladItem.js

      This component will have three parts: the name of the item, an icon showing if the item is a favorite of the user, and an emoji placed inside a button that will add the item to the salad on click. Add the following lines to SaladItem.js:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        add: {
          background: 'none',
          border: 'none',
          cursor: 'pointer',
        },
        favorite: {
          fontSize: 20,
          position: 'absolute',
          top: 10,
          right: 10,
        },
        image: {
          fontSize: 80
        },
        wrapper: {
          border: 'lightgrey solid 1px',
          margin: 20,
          padding: 25,
          position: 'relative',
          textAlign: 'center',
          textTransform: 'capitalize',
          width: 200,
        }
      });
      
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const favorite = true;
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      
      SaladItem.propTypes = {
        image: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
      }
      

      The image and name will be props. The code uses the favorite variable and ternary operators to conditionally determine if the favorite icon appears or not. The favorite variable will later be determined with context as part of the user’s profile. For now, set it to true. The styling will place the favorite icon in the upper right corner of the card and remove the default border and background on the button. The wrapper class will add a small border and transform some of the text. Finally, PropTypes adds a weak typing system to provide some enforcement to make sure the wrong prop type is not passed.

      Save and close the file. Now, you’ll need to render the different items. You’ll do that with a component called SaladBuilder, which will contain a list of items that it will convert to a series of SaladItem components:

      Open SaladBuilder:

      • nano src/components/SaladBuilder/SaladBuilder.js

      If this were a production app, this data would often come from an Application Programming Interface (API). But for now, use a hard-coded list of ingredients:

      state-context-tutorial/src/components/SaladBuilder/SaladBuilder.js

      import React from 'react';
      import SaladItem from '../SaladItem/SaladItem';
      
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          display: 'flex',
          flexWrap: 'wrap',
          padding: [10, 50],
          justifyContent: 'center',
        }
      });
      
      const ingredients = [
        {
          image: '🍎',
          name: 'apple',
        },
        {
          image: '🥑',
          name: 'avocado',
        },
        {
          image: '🥦',
          name: 'broccoli',
        },
        {
          image: '🥕',
          name: 'carrot',
        },
        {
          image: '🍷',
          name: 'red wine dressing',
        },
        {
          image: '🍚',
          name: 'seasoned rice',
        },
      ];
      
      export default function SaladBuilder() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            {
              ingredients.map(ingredient => (
                <SaladItem
                  key={ingredient.name}
                  image={ingredient.image}
                  name={ingredient.name}
                />
              ))
            }
          </div>
        )
      }
      

      This snippet uses the map() array method to map over each item in the list, passing the name and image as props to a SaladItem component. Be sure to add a key to each item as you map. The styling for this component adds a display of flex for the flexbox layout, wraps the components, and centers them.

      Save and close the file.

      Finally, render the component in SaladMaker so it will appear in the page.

      Open SaladMaker:

      • nano src/components/SaladMaker/SaladMaker.js

      Then import SaladBuilder and render after the heading:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
          </>
        )
      }
      

      Save and close the file. When you do the page will reload and you’ll find the content:

      Salad Builder with Items

      The last step is to add the summary of the salad in progress. This component will show a list of items a user has selected. For now, you’ll hard-code the items. You’ll update them with context in Step 3.

      Open SaladSummary in your text editor:

      • nano src/components/SaladSummary/SaladSummary.js

      The component will be a heading and an unsorted list of items. You’ll use flexbox to make them wrap:

      state-context-tutorial/src/components/SaladSummary/SaladSummary.jss

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        list: {
          display: 'flex',
          flexDirection: 'column',
          flexWrap: 'wrap',
          maxHeight: 50,
          '& li': {
            width: 100
          }
        },
        wrapper: {
          borderTop: 'black solid 1px',
          display: 'flex',
          padding: 25,
        }
      });
      
      export default function SaladSummary() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            <h2>Your Salad</h2>
            <ul className={classes.list}>
              <li>Apple</li>
              <li>Avocado</li>
              <li>Carrots</li>
            </ul>
          </div>
        )
      }
      

      Save the file. Then open SaladMaker to render the item:

      • nano src/components/SaladMaker/SaladMaker.js

      Import and add SaladSummary after the SaladBuilder:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      Save and close the file. When you do, the page will refresh and you’ll find the full application:

      Salad Builder Site

      There is shared data throughout the application. The Navigation component and the SaladItem component both need to know something about the user: their name and their list of favorites. The SaladItem also needs to update data that is accessible in the SaladSummary component. The components share common ancestors, but passing the data down through the tree would be difficult and error prone.

      That’s where context comes in. You can declare the data in a common parent and then access later without explicitly passing it down the hierarchy of components.

      In this step, you created an application to allow the user to build a salad from a list of options. You created a set of components that need to access or update data that is controlled by other components. In the next step, you’ll use context to store data and access it in child components.

      Step 2 — Providing Data from a Root Component

      In this step, you’ll use context to store the customer information at the root of the component. You’ll create a custom context, then use a special wrapping component called a Provider that will store the information at the root of the project. You’ll then use the useContext Hook to connect with the provider in nested components so you can display the static information. By the end of this step, you’ll be able to provide centralized stores of information and use information stored in a context in many different components.

      Context at its most basic is an interface for sharing information. Many applications have some universal information they need to share across the application, such as user preferences, theming information, and site-wide application changes. With context, you can store that information at the root level then access it anywhere. Since you set the information in a parent, you know it will always be available and it will always be up-to-date.

      To add a context, create a new directory called User:

      • mkdir src/components/User

      User isn’t going to be a traditional component, in that you are going to use it both as a component and as a piece of data for a special Hook called useContext. For now, keep the flat file structure, but if you use a lot of contexts, it might be worth moving them to a different directory structure.

      Next, open up User.js in your text editor:

      • nano src/components/User/User.js

      Inside the file, import the createContext function from React, then execute the function and export the result:

      state-context-tutorial/src/components/User/User.js

      import { createContext } from 'react';
      
      const UserContext = createContext();
      export default UserContext;
      

      By executing the function, you have registered the context. The result, UserContext, is what you will use in your components.

      Save and close the file.

      The next step is to apply the context to a set of elements. To do that, you will use a component called a Provider. The Provider is a component that sets the data and then wraps some child components. Any wrapped child components will have access to data from the Provider with the useContext Hook.

      Since the user data will be constant across the project, put it as high up the component tree as you can. In this application, you will put it at the root level in the App component:

      Open up App:

      • nano src/components/App/App.js

      Add in the following highlighted lines of code to import the context and pass along the data:

      state-context-tutorial/src/components/App/App.js

      import React from 'react';
      import Navigation from '../Navigation/Navigation';
      import SaladMaker from '../SaladMaker/SaladMaker';
      import UserContext from '../User/User';
      
      const user = {
        name: 'Kwame',
        favorites: [
          'avocado',
          'carrot'
        ]
      }
      
      function App() {
        return (
          <UserContext.Provider value={user}>
            <Navigation />
            <SaladMaker />
          </UserContext.Provider>
        );
      }
      
      export default App;
      

      In a typical application, you would fetch the user data or have it stored during a server-side render. In this case, you hard-coded some data that you might receive from an API. You created an object called user that holds the username as a string and an array of favorite ingredients.

      Next, you imported the UserContext, then wrapped Navigation and SaladMaker with a component called the UserContext.Provider. Notice how in this case UserContext is acting as a standard React component. This component will take a single prop called value. That prop will be the data you want to share, which in this case is the user object.

      Save and close the file. Now the data is available throughout the application. However, to use the data, you’ll need to once again import and access the context.

      Now that you have set context, you can start replacing hard-coded data in your component with dynamic values. Start by replacing the hard-coded name in Navigation with the user data you set with UserContext.Provider.

      Open Navigation.js:

      • nano src/components/Navigation/Navigation.js

      Inside of Navigation, import the useContext Hook from React and UserContext from the component directory. Then call useContext using UserContext as an argument. Unlike the UserContext.Provider, you do not need to render UserContext in the JSX. The Hook will return the data that you provided in the value prop. Save the data to a new variable called user, which is an object containing name and favorites. You can then replace the hard-coded name with user.name:

      state-context-tutorial/src/components/Navigation/Navigation.js

      import React, { useContext } from 'react';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      
      const useStyles = createUseStyles({
        wrapper: {
          outline: 'black solid 1px',
          padding: [15, 10],
          textAlign: 'right',
        }
      });
      
      export default function Navigation() {
        const user = useContext(UserContext);
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            Welcome, {user.name}
          </div>
        )
      }
      

      UserContext worked as a component in App.js, but here you are using it more as a piece of data. However, it can still act as a component if you would like. You can access the same data by using a Consumer that is part of the UserContext. You retrieve the data by adding UserContext.Consumer to your JSX, then use a function as a child to access the data.

      While it’s possible to use the Consumer component, using Hooks can often be shorter and easier to read, while still providing the same up-to-date information. This is why this tutorial uses the Hooks approach.

      Save and close the file. When you do, the page will refresh and you’ll see the same name. But this time it has updated dynamically:

      Salad Builder Site

      In this case the data didn’t travel across many components. The component tree that represents the path that the data traveled would look like this:

      | UserContext.Provider
        | Navigation
      

      You could pass this username as a prop, and at this scale that could be an effective strategy. But as the application grows, there’s a chance that the Navigation component will move. There may be a component called Header that wraps the Navigation component and another component such as a TitleBar, or maybe you’ll create a Template component and then nest the Navigation in there. By using context, you won’t have to refactor Navigation as long as the Provider is up the tree, making refactoring easier.

      The next component that needs user data is the SaladItem component. In the SaladItem component, you’ll need the user’s array of favorites. You’ll conditionally display the emoji if the ingredient is a favorite of the user.

      Open SaladItem.js:

      • nano src/components/SaladItem/SaladItem.js

      Import useContext and UserContext, then call useContext with UserContext. After that, check to see if the ingredient is in the favorites array using the includes method:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      
      const useStyles = createUseStyles({
      ...
      });
      
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const user = useContext(UserContext);
        const favorite = user.favorites.includes(name);
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      
      SaladItem.propTypes = {
        image: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
      }
      

      Save and close the file. When you do, the browser will refresh and you’ll see that only the favorite items have the emoji:

      Salad Maker with Avocado and Carrot favorited

      Unlike Navigation, the context is traveling much farther. The component tree would look something like this:

      | User.Provider
        | SaladMaker
          | SaladBuilder
            | SaladItem
      

      The information skipped over two intermediary components without any props. If you had to pass the data as a prop all the way through the tree, it would be a lot of work and you’d risk having a future developer refactor the code and forget to pass the prop down. With context, you can be confident the code will work as the application grows and evolves.

      In this step, you created a context and used a Provider to set the data in the component tree. You also accessed context with the useContext Hook and used context across multiple components. This data was static and thus never changed after the initial set up, but there are going to be times when you need to share data and also modify the data across multiple components. In the next step, you’ll update nested data using context.

      Step 3 — Updating Data from Nested Components

      In this step, you’ll use context and the useReducer Hook to create dynamic data that nested components can consume and update. You’ll update your SaladItem components to set data that the SaladSummary will use and display. You’ll also set context providers outside of the root component. By the end of this step, you’ll have an application that can use and update data across several components and you’ll be able to add multiple context providers at different levels of an application.

      At this point, your application is displaying user data across multiple components, but it lacks any user interaction. In the previous step, you used context to share a single piece of data, but you can also share a collection of data, including functions. That means you can share data and also share the function to update the data.

      In your application, each SaladItem needs to update a shared list. Then your SaladSummary component will display the items the user has selected and add it to the list. The problem is that these components are not direct descendants, so you can’t pass the data and the update functions as props. But they do share a common parent: SaladMaker.

      One of the big differences between context and other state management solutions such as Redux is that context is not intended to be a central store. You can use it multiple times throughout an application and initiate it at the root level or deep in a component tree. In other words, you can spread your contexts throughout the application, creating focused data collections without worrying about conflicts.

      To keep context focused, create Providers that wrap the nearest shared parent when possible. In this case, that means, rather than adding another context in App, you will add the context in the SaladMaker component.

      Open SaladMaker:

      • nano src/components/SaladMaker/SaladMaker.js

      Then create and export a new context called SaladContext:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      In the previous step, you made a separate component for your context, but in this case you are creating it in the same file that you are using it. Since User does not seem related directly to the App, it might make more sense to keep them separate. However, since the SaladContext is tied closely to the SaladMaker component, keeping them together will create more readable code.

      In addition, you could create a more generic context called OrderContext, which you could reuse across multiple components. In that case, you’d want to make a separate component. For now, keep them together. You can always refactor later if you decide to shift to another pattern.

      Before you add the Provider think about the data that you want to share. You’ll need an array of items and a function for adding the items. Unlike other centralized state management tools, context does not handle updates to your data. It merely holds the data for use later. To update data, you’ll need to use other state management tools such as Hooks. If you were collecting data for the same component, you’d use either the useState or useReducer Hooks. If you are new to these Hooks, check out How To Manage State with Hooks on React Components.

      The useReducer Hook is a good fit since you’ll need to update the most recent state on every action.

      Create a reducer function that adds a new item to a state array, then use the useReducer Hook to create a salad array and a setSalad function:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { useReducer, createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      function reducer(state, item) {
        return [...state, item]
      }
      
      export default function SaladMaker() {
        const classes = useStyles();
        const [salad, setSalad] = useReducer(reducer, []);
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      Now you have a component that contains the salad data you want to share, a function called setSalad to update the data, and the SaladContext to share the data in the same component. At this point, you need to combine them together.

      To combine, you’ll need to create a Provider. The problem is that the Provider takes a single value as a prop. Since you can’t pass salad and setSalad individually, you’ll need to combine them into an object and pass the object as the value:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { useReducer, createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      function reducer(state, item) {
        return [...state, item]
      }
      
      export default function SaladMaker() {
        const classes = useStyles();
        const [salad, setSalad] = useReducer(reducer, []);
        return(
          <SaladContext.Provider value={{ salad, setSalad }}>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </SaladContext.Provider>
        )
      }
      

      Save and close the file. As with Navigation, it may seem unnecessary to create a context when the SaladSummary is in the same component as the context. Passing salad as a prop is perfectly reasonable, but you may end up refactoring it later. Using context here keeps the information together in a single place.

      Next, go into the SaladItem component and pull the setSalad function out of the context.

      Open the component in a text editor:

      • nano src/components/SaladItem/SaladItem.js

      Inside SaladItem, import the context from SaladMaker, then pull out the setSalad function using destructuring. Add a click event to the button that will call the setSalad function. Since you want a user to be able to add an item multiple times, you’ll also need to create a unique id for each item so that the map function will be able to assign a unique key:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React, { useReducer, useContext } from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      import { SaladContext } from '../SaladMaker/SaladMaker';
      
      const useStyles = createUseStyles({
      ...
      });
      
      const reducer = key => key + 1;
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const { setSalad } = useContext(SaladContext)
        const user = useContext(UserContext);
        const favorite = user.favorites.includes(name);
        const [id, updateId] = useReducer(reducer, 0);
        function update() {
          setSalad({
            name,
            id: `${name}-${id}`
          })
          updateId();
        };
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add} onClick={update}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      ...
      

      To make the unique id, you’ll use the useReducer Hook to increment a value on every click. For the first click, the id will be 0; the second will be 1, and so on. You’ll never display this value to the user; this will just create a unique value for the mapping function later.

      After creating the unique id, you created a function called update to increment the id and to call setSalad. Finally, you attached the function to the button with the onClick prop.

      Save and close the file. The last step is to pull the dynamic data from the context in the SaladSummary.

      Open SaladSummary:

      • nano src/components/SaladSummary/SaladSummary.js

      Import the SaladContext component, then pull out the salad data using destructuring. Replace the hard-coded list items with a function that maps over salad, converting the objects to <li> elements. Be sure to use the id as the key:

      state-context-tutorial/src/components/SaladSummary/SaladSummary.js

      import React, { useContext } from 'react';
      import { createUseStyles } from 'react-jss';
      
      import { SaladContext } from '../SaladMaker/SaladMaker';
      
      const useStyles = createUseStyles({
      ...
      });
      
      export default function SaladSummary() {
        const classes = useStyles();
        const { salad } = useContext(SaladContext);
        return(
          <div className={classes.wrapper}>
            <h2>Your Salad</h2>
            <ul className={classes.list}>
              {salad.map(({ name, id }) => (<li key={id}>{name}</li>))}
            </ul>
          </div>
        )
      }
      

      Save and close the file. When you do, you will be able to click on items and it will update the summary:

      Adding salad items

      Notice how the context gave you the ability to share and update data in different components. The context didn’t update the items itself, but it gave you a way to use the useReducer Hook across multiple components. In addition, you also had the freedom to put the context lower in the tree. It may seem like it’s best to always keep the context at the root, but by keeping the context lower, you don’t have to worry about unused state sticking around in a central store. As soon as you unmount a component, the data disappears. That can be a problem if you ever want to save the data, but in that case, you just need to raise the context up to a higher parent.

      Another advantage of using context lower in your application tree is that you can reuse a context without worrying about conflicts. Suppose you had a larger app that had a sandwich maker and a salad maker. You could create a generic context called OrderContext and then you could use it at multiple points in your component without worrying about data or name conflicts. If you had a SaladMaker and a SandwichMaker, the tree would look something like this:

      | App
        | Salads
          | OrderContext
            | SaladMaker
        | Sandwiches
          | OrderContext
            | SandwichMaker
      

      Notice that OrderContext is there twice. That’s fine, since the useContext Hook will look for the nearest provider.

      In this step you shared and updated data using context. You also placed the context outside the root element so it’s close to the components that need the information without cluttering a root component. Finally, you combined context with state management Hooks to create data that is dynamic and accessible across several components.

      Conclusion

      Context is a powerful and flexible tool that gives you the ability to store and use data across an application. It gives you the ability to handle distributed data with built-in tools that do not require any additional third party installation or configuration.

      Creating reusable contexts is important across a variety of common components such as forms that need to access data across elements or tab views that need a common context for both the tab and the display. You can store many types of information in contexts including themes, form data, alert messages, and more. Context gives you the freedom to build components that can access data without worrying about how to pass data through intermediary components or how to store data in a centralized store without making the store too large.

      If you would like to look at more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link

      How To Record and Share Terminal Sessions Using Terminalizer on Ubuntu 18.04


      The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Terminalizer is a terminal recorder application that allows you to record your terminal session in real-time, and then play it back at a later date. It works in the same way as a desktop screen recorder, but instead runs in your terminal.

      Recording your terminal session is useful if you want to review a particular activity again, or to help debug a particularly tricky error. Recordings made with Terminalizer can also be exported as animated GIFs, which are great for sharing online or adding to marketing material for your software.

      In this tutorial, you will install Terminalizer, use it to record and play back terminal sessions, customize your recordings, and then export them to share online.

      Prerequisites

      To complete this tutorial, you will need:

      If you wish to share your recordings online, you’ll also need:

      Once you have these ready, log in to your server as your non-root user to begin.

      Step 1 — Installing Terminalizer

      In this step, you will download and install Terminalizer on your system. Terminalizer is written using Node.js, and is available to install using the npm package manager.

      To install Terminalizer globally on your system, run the following command:

      • sudo npm install --global --allow-root --unsafe-perm=true terminalizer

      Terminalizer uses the Electron application framework to export recorded terminal sessions into GIF format. The --unsafe-perms=true command argument is required in order to install Electron globally on your system.

      Once Terminalizer has been installed, you’ll see similar output to the following:

      Output

      . . . /usr/local/lib └── terminalizer@0.7.1

      Next, check your installation of Terminalizer by running:

      This will display something similar to the following:

      Output

      0.7.1

      Finally, generate a default Terminalizer configuration file, which you can use for Terminalizer’s advanced customization (detailed further in Step 4):

      This will produce output similar to the following:

      Output

      The global config directory is created at /home/user/.terminalizer

      Now that you’ve installed Terminalizer, you can make your first terminal recording.

      Step 2 — Recording and Playing Back a Terminal Session

      In this step, you will record and playback a terminal session.

      To begin, set up a new Terminalizer recording using a name of your choice:

      • terminalizer record your-recording

      This will output the following to indicate that the recording has started:

      Output

      The recording session has started Press Ctrl+D to exit and save the recording

      You can now proceed to do anything that you want within your terminal. Each key press and command will be recorded in real-time by Terminalizer.

      For example:

      • pwd
      • date
      • whoami
      • echo "Hello, world!"

      When you’d like to stop the recording, press CTRL+D. Terminalizer will then save the recording to the specified file in YAML format, for example, your-recording.yml.

      Output

      Successfully Recorded The recording data is saved into the file: /home/user/your-recording.yml

      You may be prompted by Terminalizer to share your recording online. Just press CTRL+C to cancel this for now, as you can playback the terminal recording locally first.

      Next, play your recorded terminal session with the followng command:

      • terminalizer play your-recording

      This will replay the recorded session in real-time in your terminal:

      Output

      user@droplet:~$ pwd /home/user user@droplet:~$ date Sun Mar 8 14:55:36 UTC 2020 user@droplet:~$ whoami user user@droplet:~$ echo "Hello, world!" Hello, world! user@droplet:~$ logout

      You can also adjust the playback speed of your recording using the --speed-factor option.

      For example, the following will playback your recording twice as slowly (half speed):

      • terminalizer play your-recording --speed-factor 2

      Alternatively, you can play back your recording twice as fast (double speed):

      • terminalizer play your-recording --speed-factor 0.5

      You’ve recorded and played back a terminal session. Next, you can share a recorded terminal session online.

      Step 3 — Sharing a Recorded Terminal Session

      In this step, you’ll share your recorded terminal session online on the Terminalizer Explore page.

      Begin by selecting a recorded session to share:

      • terminalizer share your-recording

      You will then be prompted to provide some basic metadata about your recording, such as the title and description:

      Output

      Please enter some details about your recording ? Title Title of Your Recording ? Description Description of Your Recording ? Tags such as git,bash,game Comma-separated Tags for Your Recording

      Warning: Terminalizer recordings are shared publicly by default, so ensure that there are no personally identifiable or confidential details present in your terminal recording that you don’t want to share.

      If this is the first time that you’ve shared a recorded session using Terminalizer, you’ll need to link your Terminalizer account. Terminalizer will display a verification link if this is required:

      Output

      Open the following link in your browser and login into your account https://terminalizer.com/token?token=your-token When you do it, press any key to continue

      Warning: Ensure that you keep your Terminalizer token private, as it will allow anyone in possession of it to access your Terminalizer account.

      Once you have visited the link in your web browser and signed in to your Terminalizer account, press any key to continue.

      Terminalizer will now upload your recording and provide you the link to view it:

      Output

      Successfully Uploaded The recording is available on the link: https://terminalizer.com/view/your-recording-id

      Visiting the link in a desktop web browser will allow you to view your shared recording:

      A screenshot of the Terminalizer website, showing an example of a shared terminal recording

      You’ve shared a recorded terminal session on the Terminalizer website and viewed it in your web browser.

      Step 4 — Setting Advanced Terminalizer Configuration

      Now that you’ve gained some familiarity with Terminalizer, you can begin to review some of the more advanced customization options, such as the ability to adjust the display colors and style.

      Each recording inherits the default configuration from the global Terminalizer config file, which is located at ~/.terminalizer/config.yml. This means that you can edit the configuration for individual recordings directly by editing the recording file (e.g. your-recording.yml). Alternatively, you can edit the global configuration, which will have an impact on all new recordings.

      In this example you’ll edit the global configuration file, but the same guidance applies to individual recording configuration files as well.

      Begin by opening the global Terminalizer configuration file in your text editor, such as nano:

      • nano ~/.terminalizer/config.yml

      Each of the available configuration options within the file are commented in order to explain what they do.

      There are several common configuration options that you may wish to adjust to your liking:

      • cols: Explicitly set the number of terminal columns used for your recording.
      • rows: Explicitly set the number of terminal rows used for your recording.
      • frameDelay: Override the delay between each keystroke during playback.
      • maxIdleTime: Specify a maximum time between keystrokes during playback.
      • cursorStyle: Specify the default terminal cursor style out of block, bar, and underline.
      • fontFamily: Specify a list of preferred playback fonts, in order of preference.
      • theme: Adjust the color scheme of the playback, for example to create a black-on-white terminal, etc.

      As an example, you can achieve a white-on-black terminal display by configuring the following options:

      config.yml

      . . .
      theme:
        background: "white"
        foreground: "black"
      . . .
      

      This will produce a result similar to the following:

      A screenshot of the Terminalizer website, showing an example of a recording with a black-on-white theme

      You could adjust the cursor style to make the recording easier to understand, for example by swapping the default block-style cursor with an underlined one:

      config.yml

      . . .
      cursorStyle: underline
      . . .
      

      This produces a result similar to the following:

      A screenshot of the Terminalizer website, showing an example of a recording with an underline-style cursor

      Once you have made any desired changes, save the file and return to your terminal.

      If you edited the global Terminalizer configuration, these settings will apply to all new recordings going forward. If you’re editing a specific recording configuration, Terminalizer will immediately apply the changes to that particular recording.

      Note that custom playback styling only applies to shared recording sessions. Playing them back directly in your terminal will always use your default terminal styling and color scheme.

      In this final step, you reviewed some of the advanced configuration options for Terminalizer.

      Conclusion

      In this article you used Terminalizer to record and share a terminal session. You now have the knowledge required to create recorded demos of your software for use in marketing material, or to share command-line tricks with friends.

      If you wish to render and export Terminalizer recordings into GIF format, you can install Terminalizer on a machine with a graphical user interface/desktop and use the built-in rendering features:

      You may also wish to browse the Terminalizer website to see recorded terminal sessions shared by other users:



      Source link

      How To Sync and Share Your Files with Seafile on Debian 10


      Introduction

      Seafile is an open-source, self-hosted, file synchronization and sharing platform. Users can store and optionally encrypt data on their own servers with storage space as the only limitation. With Seafile you can share files and folders using cross-platform syncing and password-protected links to files with expiration dates. A file-versioning feature means that users can restore deleted and modified files or folders.

      In this tutorial, you will install and configure Seafile on a Debian 10 server. You will use MariaDB (the default MySQL variant on Debian 10) to store data for the different components of Seafile, and Apache as the proxy server to handle the web traffic. After completing this tutorial, you will be able use the web interface to access Seafile from desktop or mobile clients, allowing you to sync and share your files with other users or groups on the server or with the public.

      Prerequisites

      Before you begin this guide, you’ll need the following:

      Step 1 — Creating Databases for the Seafile Components

      Seafile requires three components in order to work properly:

      • Seahub: Seafile’s web frontend, written in Python using the Django web framework. From Seahub you can access, manage, and share your files using a web browser.
      • Seafile server: The data service daemon that manages the raw file upload, download, and synchronization. You do not interact with the server directly, but instead use a client program or the Seahub web interface.
      • Ccnet server: The RPC service daemon to enable internal communication between the different components of Seafile. For example, when you use Seahub, it is able to access data from the Seafile server using the Ccnet RPC service.

      Each of these components stores its data separately in its own database. In this step you will create the three MariaDB databases and a user before proceeding to set up the server.

      First, log in to the server using SSH with your username and IP address:

      ssh sammy@your_server_ip
      

      Connect to the MariaDB database server as administrator (root):

      At the MariaDB prompt, use the following SQL command to create the database user:

      • CREATE USER 'sammy'@'localhost' IDENTIFIED BY 'password';

      Next, you will create the following databases to store the data of the three Seafile components:

      • ccnet-db for the Ccnet server.
      • seahub-db for the Seahub web frontend.
      • seafile-db for the Seafile file server.

      At the MariaDB prompt, create your databases:

      • CREATE DATABASE 'ccnet-db' CHARACTER SET = 'utf8';
      • CREATE DATABASE 'seafile-db' CHARACTER SET = 'utf8';
      • CREATE DATABASE 'seahub-db' CHARACTER SET = 'utf8';

      Then, grant all privileges to your database user to access and make changes in these databases:

      • GRANT ALL PRIVILEGES ON 'ccnet-db'.* to 'sammy'@localhost;
      • GRANT ALL PRIVILEGES ON 'seafile-db'.* to 'sammy'@localhost;
      • GRANT ALL PRIVILEGES ON 'seahub-db'.* to 'sammy'@localhost;

      Exit the MariaDB prompt by typing exit:

      Now that you have created a user and the databases required to store the data for each of the Seafile components, you will install dependencies to download the Seafile server package.

      Step 2 — Installing Dependencies and Downloading Seafile

      Some parts of Seafile are written in Python and therefore require additional Python modules and programs to work. In this step, you will install these required dependencies before downloading and extracting the Seafile server package.

      To install the dependencies using apt run the following command:

      • sudo apt install python-setuptools python-pip python-urllib3 python-requests python-mysqldb ffmpeg

      The python-setuptools and python-pip dependencies oversee installing and managing Python packages. The python-urllib3 and python-requests packages make requests to websites. Finally, the python-mysqldb is a library for using MariaDB from Python and ffmpeg handles multimedia files.

      Seafile requires Pillow, a python library for image processing, and moviepy to handle movie file thumbnails. These modules are not available in the Debian package repository, so instead install them with pip:

      • sudo pip install Pillow moviepy

      Now that you have installed the necessary dependencies, you can download the Seafile server package.

      Seafile creates additional directories during setup. To keep them all organized, create a new directory and change into it:

      You can now download the latest version (7.0.4 as of this writing) of the Seafile server from the project website by running the following command:

      • wget https://download.seadrive.org/seafile-server_7.0.4_x86-64.tar.gz

      Seafile distributes the download as a compressed tar archive, which means you will need to extract it before proceeding. Extract the archive using tar:

      • tar -zxvf seafile-server_7.0.4_x86-64.tar.gz

      Now change into the extracted directory:

      At this stage, you have downloaded and extracted the Seafile server package and have also installed the necessary dependencies. You are now ready to configure the Seafile server.

      Step 3 — Configuring the Seafile Server

      Seafile needs some information about your setup before you start the services for the first time. This includes details like the domain name, the database configuration, and the path where it will store data. To initiate the series of question prompts to provide this information, you can run the script setup_seafile_mysql.sh, which is included in the archive you extracted in the previous step.

      Run the script using bash:

      • bash setup-seafile-mysql.sh

      Press ENTER to continue when prompted.

      The script will now prompt you with a series of questions. Wherever defaults are mentioned, pressing the ENTER key will use that value.

      This tutorial uses Seafile as the server name, but you can change it if you'd like:

      Question 1
      
      What is the name of the server?
      It will be displayed on the client. 3 - 15 letters or digits
      [ server name ] Seafile
      

      For Question 2, enter the domain name for this Seafile instance.

      Question 2
      
      What is the ip or domain of the server?.
      For example: www.mycompany.com, 192.168.1.101
      [ This server's ip or domain ] your_domain
      

      Press ENTER to accept the default value for Question 3. If you have set up external storage, for example, using NFS or block storage, you will need to specify the path to that location here instead.

      Question 3
      
      Where do you want to put your seafile data?
      Please use a volume with enough free space
      [ default "/home/sammy/seafile/seafile-data" ]
      

      For Question 4 press ENTER to accept the default port number.

      Question 4
      
      Which port do you want to use for the seafile fileserver?
      [ default "8082" ]
      

      The next prompt allows you to confirm the database configuration. You can create new databases or use existing databases for setup. For this tutorial you have created the necessary databases in Step 1, so select option 2 here.

      -------------------------------------------------------
      Please choose a way to initialize seafile databases:
      -------------------------------------------------------
      
      [1] Create new ccnet/seafile/seahub databases
      [2] Use existing ccnet/seafile/seahub databases
      
      [ 1 or 2 ] 2
      

      Questions 6–9 relate to the MariaDB database server. You need to provide the username and password of the MySQL user that you created in Step 1, but you can press ENTER to accept the default values for host and port.

      
      What is the host of mysql server?
      
      [ default "localhost" ]
      
      What is the port of mysql server?
      
      [ default "3306" ]
      
      Which mysql user to use for seafile?
      
      [ mysql user for seafile ] sammy
      
      What is the password for mysql user "seafile"?
      
      [ password for seafile ] password
      

      After providing the password, the script will request the names of the Seafile databases. Use ccnet-db, seafile-db, and seahub-db for this tutorial. The script will then verify if there is a successful connection to the databases before proceeding to display a summary of the initial configuration.

      Enter the existing database name for ccnet:
      [ ccnet database ] ccnet-db
      
      verifying user "sammy" access to database ccnet-db ...  done
      
      Enter the existing database name for seafile:
      [ seafile database ] seafile-db
      
      verifying user "sammy" access to database seafile-db ...  done
      
      Enter the existing database name for seahub:
      [ seahub database ] seahub-db
      
      verifying user "sammyFor this tutorial you have" access to database seahub-db ...  done
      
      ---------------------------------
      This is your configuration
      ---------------------------------
      
          server name:            Seafile
          server ip/domain:       your_domain
      
          seafile data dir:       /home/sammy/seafile/seafile-data
          fileserver port:        8082
      
          database:               use existing
          ccnet database:         ccnet-db
          seafile database:       seafile-db
          seahub database:        seahub-db
          database user:          sammy
      
      --------------------------------
      Press ENTER to continue, or Ctrl-C to abort
      ---------------------------------
      

      Press ENTER to confirm.

      Output

      Generating ccnet configuration ... done Successly create configuration dir /home/sammy/seafile/ccnet. Generating seafile configuration ... done Generating seahub configuration ... ---------------------------------------- Now creating seahub database tables ... ---------------------------------------- creating seafile-server-latest symbolic link ... done ----------------------------------------------------------------- Your seafile server configuration has been finished successfully. ----------------------------------------------------------------- run seafile server: ./seafile.sh { start | stop | restart } run seahub server: ./seahub.sh { start <port> | stop | restart <port> } ----------------------------------------------------------------- If you are behind a firewall, remember to allow input/output of these tcp ports: ----------------------------------------------------------------- port of seafile fileserver: 8082 port of seahub: 8000 When problems occur, Refer to https://github.com/haiwen/seafile/wiki for information.

      You will be running Seafile behind Apache, which you've already allowed through your server's firewall. Hence, you don't need to worry about opening ports 8082 and 8000 as well and you can ignore that part of the output.

      You have completed the initial configuration of the server. In the next step, you will configure the Apache web server before starting the Seafile services.

      Step 4 — Configuring the Apache Web Server

      In this step, you will configure the Apache web server to forward all requests to Seafile. Using Apache in this manner allows you to use a URL without a port number, enable HTTPS connections to Seafile, and make use of the caching functionality that Apache provides for better performance.

      To begin forwarding requests, you will need to enable the proxy_http module in the Apache configuration. This module provides features for proxying HTTP and HTTPS requests. The following command will enable the module:

      Note: The Apache rewrite and ssl modules are also required for this setup. You have already enabled these modules as part of configuring Let's Encrypt in the second Apache tutorial listed in the prerequisites section.

      Next, update the virtual host configuration of your_domain to forward requests to the Seafile file server and to the Seahub web interface.

      Open the configuration file in a text editor:

      • sudo nano /etc/apache2/sites-enabled/your_domain-le-ssl.conf

      The lines from ServerAdmin to SSLCertificateKeyFile are part of the initial Apache and Let's Encrypt configuration that you set up in the prerequisite tutorials. Add the highlighted content, beginning at Alias and ending with the ProxyPassReverse directive:

      /etc/apache2/sites-enabled/your_domain-le-ssl.conf

      
      <IfModule mod_ssl.c>
      <VirtualHost *:443>
          ServerAdmin admin@your_email_domain
          ServerName your_domain
          ServerAlias www.your_domain
          DocumentRoot /var/www/your_domain/html
          ErrorLog ${APACHE_LOG_DIR}/your_domain-error.log
          CustomLog ${APACHE_LOG_DIR}/your_domain-access.log combined
      
          Include /etc/letsencrypt/options-ssl-apache.conf
          SSLCertificateFile /etc/letsencrypt/live/your_domain/fullchain.pem
          SSLCertificateKeyFile /etc/letsencrypt/live/your_domain/privkey.pem
      
          Alias /media  /home/sammy/seafile/seafile-server-latest/seahub/media
          <Location /media>
              Require all granted
          </Location>
      
          # seafile fileserver
          ProxyPass /seafhttp http://127.0.0.1:8082
          ProxyPassReverse /seafhttp http://127.0.0.1:8082
          RewriteEngine On
          RewriteRule ^/seafhttp - [QSA,L]
      
          # seahub web interface
          SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
          ProxyPass / http://127.0.0.1:8000/
          ProxyPassReverse / http://127.0.0.1:8000/
      </VirtualHost>
      </IfModule>
      

      The Alias directive maps the URL path your_domain/media to a local path in the file system that Seafile uses. The following Location directive enables access to content in this directory. The ProxyPass and ProxyPassReverse directives make Apache act as a reverse proxy for this host, forwarding requests to / and /seafhttp to the Seafile web interface and file server running on local host ports 8000 and 8082 respectively. The RewriteRule directive passes all requests to /seafhttp unchanged and stops processing further rules ([QSA,L]).

      Save and exit the file.

      Test if there are any syntax errors in the virtual host configuration:

      • sudo apache2ctl configtest

      If it reports Syntax OK, then there are no issues with your configuration. Restart Apache for the changes to take effect:

      • sudo systemctl restart apache2

      You have now configured Apache to act as a reverse proxy for the Seafile file server and Seahub. Next, you will update the URLs in Seafile's configuration before starting the services.

      Step 5 — Updating Seafile's Configuration and Starting Services

      As you are now using Apache to proxy all requests to Seafile, you will need to update the URLs in Seafile's configuration files in the conf directory using a text editor before you start the Seafile service.

      Open ccnet.conf in a text editor:

      • nano /home/sammy/seafile/conf/ccnet.conf

      Near the top of the file, within the [General] block, is the SERVICE_URL directive. It will look like this:

      Update /home/sammy/seafile/conf/ccnet.conf

      . . .
      SERVICE_URL=http://www.example.com:8000
      . . .
      

      Modify this setting to point to your domain. Be sure that the URL you provide uses the HTTPS protocol, and that it does not include any port number:

      Update /home/sammy/seafile/conf/ccnet.conf

      . . .
      SERVICE_URL = https://your_domain
      . . .
      

      Save and exit the file once you have added the content.

      Now open seahub_settings.py in a text editor:

      • nano /home/sammy/seafile/conf/seahub_settings.py

      Add a FILE_SERVER_ROOT setting in the file to specify the path where the file server is listening for file uploads and downloads:

      Update /home/sammy/seafile/conf/seahub_settings.py

      # -*- coding: utf-8 -*-
      SECRET_KEY = "..."
      FILE_SERVER_ROOT = 'https://your_domain/seafhttp'
      # ...
      

      Save and exit seahub_settings.py.

      Now you can start the Seafile service and the Seahub interface:

      • cd /home/sammy/seafile/seafile-server-7.0.4
      • ./seafile.sh start
      • ./seahub.sh start

      As this is the first time you have started the Seahub service, it will prompt you to create an admin account. Enter a valid email address and a password for this admin user:

      Output

      What is the email for the admin account? [ admin email ] admin@your_email_domain What is the password for the admin account? [ admin password ] password-here Enter the password again: [ admin password again ] password-here ---------------------------------------- Successfully created seafile admin ---------------------------------------- Seahub is started Done.

      Open https://your_domain in a web browser and log in using your Seafile admin email address and password.

      Login screen of the Seafile web interface

      Once logged in successfully, you can access the admin interface or create new users.

      Now that you have verified the web interface is working correctly, you can enable these services to start automatically at system boot in the next step.

      Step 6 — Enabling the Seafile Server to Start at System Boot

      To enable the file server and the web interface to start automatically at boot, you can create their respective systemd service files and activate them.

      Create a systemd service file for the Seafile file server:

      • sudo nano /etc/systemd/system/seafile.service

      Add the following content to the file:

      Create /etc/systemd/system/seafile.service

      [Unit]
      Description=Seafile
      After=network.target mysql.service
      
      [Service]
      Type=forking
      ExecStart=/home/sammy/seafile/seafile-server-latest/seafile.sh start
      ExecStop=/home/sammy/seafile/seafile-server-latest/seafile.sh stop
      User=sammy
      Group=sammy
      
      [Install]
      WantedBy=multi-user.target
      

      Here, the ExectStart and ExecStop lines indicate the commands that run to start and stop the Seafile service. The service will run with sammy as the User and Group. The After line specifies that the Seafile service will start after the networking and MariaDB service has started.

      Save seafile.service and exit.

      Create a systemd service file for the Seahub web interface:

      • sudo nano /etc/systemd/system/seahub.service

      This is similar to the Seafile service. The only difference is that the web interface is started after the Seafile service. Add the following content to this file:

      Create /etc/systemd/system/seahub.service

      [Unit]
      Description=Seafile hub
      After=network.target seafile.service
      
      [Service]
      Type=forking
      ExecStart=/home/sammy/seafile/seafile-server-latest/seahub.sh start
      ExecStop=/home/sammy/seafile/seafile-server-latest/seahub.sh stop
      User=sammy
      Group=sammy
      
      [Install]
      WantedBy=multi-user.target
      

      Save seahub.service and exit.

      You can learn more about systemd unit files in the Understanding Systemd Units and Unit Files tutorial.

      Finally, to enable both the Seafile and Seahub services to start automatically at boot, run the following commands:

      • sudo systemctl enable seafile.service
      • sudo systemctl enable seahub.service

      When the server is rebooted, Seafile will start automatically.

      At this point, you have completed setting up the server, and can now test each of the services.

      Step 7 — Testing File Syncing and Sharing Functionality

      In this step, you will test the file synchronization and sharing functionality of the server you have set up and ensure they are working correctly. To do this, you will need to install the Seafile client program on a separate computer and/or a mobile device.

      Visit the download page on the Seafile website and follow the instructions to install the latest version of the client program on your computer. Seafile clients are available for the various distributions of Linux (Ubuntu, Debian, Fedora, Centos/RHEL, Arch Linux), MacOS, and Windows. Mobile clients are available for Android and iPhone/iPad devices from the respective app stores.

      Once you have installed the Seafile client, you can test the file synchronization and sharing functionality.

      Open the Seafile client program on your computer or device. Accept the default location for the Seafile folder and click Next.

      In the next window, enter the server address, username, and password, then click Login.

      At the home page, right click on My Library and click Sync this library. Accept the default value for the location on your computer or device.

      Seafile client — Sync the default library

      Add a file, for example a document or a photo, into the My Library folder. After some time, the file will upload to the server. The following screenshot shows the file photo.jpg copied to the My Library folder.

      Add a file to the default library from the computer

      Now, log in to the web interface at https://your_domain and verify that your file is present on the server.

      My Library page to verify file sync

      Click on Share next to the file to generate a download link for this file that you can share.

      You have verified that the file synchronization is working correctly and that you can use Seafile to sync and share files and folders from multiple devices.

      Conclusion

      In this tutorial you set up a private instance of a Seafile server. Now you can start using the server to synchronize files, add users and groups, and share files between them or with the public without relying on an external service.

      When a new release of the server is available, please consult the upgrade section of the manual for steps to perform an upgrade.



      Source link