Hello I am having trouble passing props between components. I can't share the exact code so I made a simplified version. I am not getting any console errors, though login is obviously 'undefined' Any insight is appreciated!
App.js
import React, { useState } from "react";
function App() {
const [login, setLogin] = useState('Jpm91297');
const changeState = () => {
const newLogin = document.getElementById('loginData').value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={() => changeState()}>
<input type='text' id='loginData'></input>
<button>Submit</button>
</form>
</>
);
}
export default App;
Api.Js
import React, {useEffect, useState} from "react";
const Api = ( { login } ) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, []);
if (data) {
return <div>{JSON.stringify(data)}</div>
}
return <div>No data Avail</div>
}
export default Api;
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api'
ReactDOM.render(
<>
<App />
<Api />
</>,
document.getElementById('root')
);
You are not preventing the default form action from occurring. This reloads the app.
You should lift the login state to the common parent of App and Api so it can be passed down as a prop. See Lifting State Up.
Example:
index.js
Move the login state to a parent component so that it can be passed down as props to the children components that care about it.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Api from './api';
const Root = () => {
const [login, setLogin] = useState('Jpm91297');
return (
<>
<App setLogin={setLogin} />
<Api login={login} />
</>
);
};
ReactDOM.render(
<Root />,
document.getElementById('root')
);
App
Pass the changeState callback directly as the form element's onSubmit handler and prevent the default action. Access the form field from the onSubmit event object.
function App({ setLogin }) {
const changeState = (event) => {
event.preventDefault();
const newLogin = event.target.loginData.value;
setLogin(newLogin);
}
return (
<>
<h1>Fancy API Call!!!</h1>
<form onSubmit={changeState}>
<input type='text' id='loginData'></input>
<button type="submit">Submit</button>
</form>
</>
);
}
Api
const Api = ({ login }) => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData);
}, [login]); // <-- add login dependency so fetch is made when login changes
if (data) {
return <div>{JSON.stringify(data)}</div>;
}
return <div>No data Avail</div>;
};
Related
I have sorted the posts with get method using typicode. Now I want to add it myself using the post method. How can I do it properly?
The problem here is that even if it is posted, it does not appear in the posts list. I want it to appear in all posts when I add it myself and see it in the ranking. How can I do it?
import axios from "axios";
import React from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
const Form = ({ mainData, setData }) => {
const [formData, setFormData] = useState({
title: "",
body: "",
});
const history = useNavigate();
const onChange = (e) => {
setFormData({[e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();
history("/");
axios
.post("https://jsonplaceholder.typicode.com/posts", formData)
.then((res) => {
console.log(res);
});
};
return (
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="your title"
name="title"
value={formData.title}
onChange={onChange}
/>
<input
type="text"
placeholder="body"
name="body"
value={formData.body}
onChange={onChange}
/>
<button type="submit">Submit</button>
</form>
);
};
export default Form;
import React from "react";
import axios from "axios";
import { useEffect, useState } from "react";
import { NavLink } from "react-router-dom";
const Posts = ({mainData, setData}) => {
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((res) => res)
.then((res) => {
setData(res.data);
console.log(mainData);
});
}, []);
return (
<>
<NavLink to="/new">Add new post</NavLink>
{mainData?.map((item, index) => (
<h4 key={index}>
{item.id} : {item.title}
</h4>
))}
</>
);
};
export default Posts;
import React, {useState} from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Form from "../Form";
import Posts from "../Posts";
const Rout = () => {
const [mainData, setData] = useState([]);
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Posts mainData={mainData} setData={setData}/>} ></Route>
<Route path="/new" element={<Form mainData={mainData} setData={setData}/>}></Route>
</Routes>
</BrowserRouter>
);
};
export default Rout;
A post request made to JSONPlaceholder is juste for testing, it doesn't really get registered in its database. Here a quote from their doc:
Important: resource will not be really updated on the server but it will be faked as if.
When you redirect to "/", Posts will fetch data but it won't contain what you just added inside Form.
I tried making a global state by using a Context and have the following for my AuthProvider.js file
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
I want to be able to change its boolean state within an if-else instead of a button with an onCLick event in the tutorial but it shows an error in the console: setAuth is not a function
function Home() {
const setAuth = useContext(AuthProvider);
const [usernameReg, setUsernameReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loginStatus, setloginStatus] = useState("");
const uLogin = document.getElementById('userLogin');
const pLogin = document.getElementById('passwordLogin');
const register = () => {
if (usernameReg === "" || passwordReg === ""){
alert("Fields can't be blank");
return;
}
Axios.post('http://localhost:3001/register', {
username: usernameReg,
password: passwordReg,
}).then((response) => {
console.log(response);
});
}
const login = () => {
Axios.post('http://localhost:3001/login', {
username: username,
password: password,
}).then((response) => {
if (response.data.message){
setloginStatus(response.data.message);
alert(response.data.message);
}
else{
setloginStatus(response.data[0].username);
setAuth(true); <====================================== here
window.location.href = "/map";
}
console.log(response.data);
});
}
return (
<div className="Home">
<h1>Register</h1>
<label>Username: </label>
<input type="text"
onChange={(e) => {
setUsernameReg(e.target.value)
}}/>
<label><p></p>Password: </label>
<input type="text"
onChange={(e)=> {
setPasswordReg(e.target.value)
}}/>
<p></p>
<button onClick={register}> Register </button>
<h1>--------------------------------------------------------</h1>
{/*<form id="loginForm">*/}
<h1>Log In</h1>
<input id="userLogin" type="text" placeholder="Username"
onChange={(e) => {
setUsername(e.target.value)
}}
/>
<p></p>
<input id="passwordLogin" type="password" placeholder="Password"
onChange={(e) => {
setPassword(e.target.value)
}}
/>
<p></p>
<button onClick={login}> Log In </button>
{/*</form>*/}
<h1>{loginStatus}</h1>
</div>
)
}
my App.js is below
import React, { useState } from "react";
import { BrowserRouter,Route, Routes} from 'react-router-dom';
import './App.css';
import Home from "./Pages/Home";
import MapPage from "./Pages/MapPage.js";
import ProtectedRoutes from "./ProtectedRoutes";
import { AuthProvider } from "./components/AuthProvider"
function App() {
const [auth, setAuth ] = useState(false);
//const [auth, setAuth] = useState(false);
return (
<BrowserRouter>
<Routes element ={<AuthProvider />} value={{auth, setAuth}}>
<Route element ={<ProtectedRoutes />}>
<Route element={<MapPage />} path="/map" exact/>
</Route>
<Route path="/" element={<Home />}/>
</Routes>
</BrowserRouter>
);
}
export default App;
index.js below
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AuthProvider } from './components/AuthProvider'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<AuthProvider>
<App />
</AuthProvider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bitly/CRA-vitals
reportWebVitals();
I'm very new to JavaScript and I've only been following along tutorials for the functions or features I need for the project. Is there a way for this?
I'm also guessing that the code for the context file is overkill for a simple Boolean state.
You probably want to check the context docs again, and try to understand how and why you set them up, and how to access them. You tried accessing the context by passing the provider as a param to the useContext hook. This is wrong, you need to pass the Context itself. You also called the return from the context setAuth, but the return is the context itself. I urge you to read the documentation thourougly. However, this is probably what you wanted:
type AuthContextType = {
auth: boolean;
setAuth: Dispatch<SetStateAction<boolean>>;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const [auth, setAuth] = useState(false);
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
);
};
const useAuthContext = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("No AuthContext Provider found");
return ctx // as AuthContextType<...> if you need the context to be generic and to infer generics.
};
function Home() {
const { auth, setAuth } = useAuthContext();
...
}
I'm trying to create a darkmode library (named react-goodnight) based on https://github.com/luisgserrano/react-dark-mode.
This is where the context is created.
import React from 'react'
const ThemeContext = React.createContext({
theme: '',
toggle: () => {}
})
export default ThemeContext
This is my useDarkMode hook that get/sets the theme to localStorage.
import { useState, useEffect } from 'react'
const useDarkMode = () => {
const [theme, setTheme] = useState('light')
const setMode = (mode) => {
window.localStorage.setItem('theme', mode)
setTheme(mode)
}
const toggle = () => (theme === 'light' ? setMode('dark') : setMode('light'))
useEffect(() => {
const localTheme = window.localStorage.getItem('theme')
localTheme && setTheme(localTheme)
}, [])
return [theme, toggle]
}
export default useDarkMode
This is the index of my library (react-goodnight).
import React, { useContext } from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyles } from './globalStyles'
import { lightTheme, darkTheme } from './settings'
import ThemeContext from './themeContext'
import useDarkMode from './useDarkMode'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</ThemeProvider>
)
}
export const useDarkModeContext = () => useContext(ThemeContext)
export default Provider
And, in the end, this is my example app where I'm trying to use it.
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
const { theme, toggle } = useDarkModeContext();
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The "Toggle" button in the library's index works fine but the one in my example app does not.
The useDarkModeContext() returns empty.
What could be the issue?
Thanks!
You are doing wrong
1st option
you can use react-goodnight provider with your index.js and use useDarkModeContext(), don't name your index.js Provider else you can not use Provider coming from react-goodnight
import Provider, { useDarkModeContext } from 'react-goodnight'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<Provider>
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</Provider>
</ThemeProvider>
)
}
2nd Option
you are passing ThemeContext in your index.js so you can also access that in app.js
import React, { useContext } from 'react'
import ThemeContext from './themeContext'
const App = () => {
const theme = useContext(ThemeContext);
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The reason it's not working is because you are calling useContext in the very same place where you print Provider.
Why is that wrong? Because useContext looks for parent context providers. By rendering Provider in the same place you call useContext, there is no parent to look for. The useContext in your example is actually part of App component, who is not a child of Provider.
All you have to do is move the button outside of that print, to its own component, and only there do useContext (or in your case the method called useDarkModeContext.
The only change would be:
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
return (
<Provider>
<div>hey</div>
<ToggleThemeButton />
</Provider>
)
}
export default App
const ToggleThemeButton = () => {
const { theme, toggle } = useDarkModeContext();
return (
<button onClick={toggle}>Switch Theme outside</button>
);
};
In my SPA, I am utilizing react hooks and context API. I need to persist the current state of the component view rendered using the context API so that I can implement the global component conditional rendering through the application.
I have two views on a single dashboard page: overview & detail. The button triggers the global state change and the view should be fixed on the state value even on page refresh.
Here's my code snippets:
AppRoutes file
import React, { useState } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "../utils/history";
import { PyramidProvider } from "../context/pyramidContext";
import Dashboard from "../pages/dashboard/Dashboard";
const AppRoutes = () => {
return (
<div>
<React.Suspense fallback={<span>Loading...</span>}>
<Router history={history}>
<Switch>
<PyramidProvider>
<Route path="/" component={Dashboard} />
</PyramidProvider>
</Switch>
</Router>
</React.Suspense>
</div>
);
};
export default AppRoutes;
Dashboard page
import React, { useState, useEffect, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
import PyramidDetail from "../../components/pyramidUI/pyramidDetail";
import PyramidOverview from "../../components/pyramidUI/pyramidOverview";
const Dashboard = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<React.Fragment>
{info.uiname === "overview" ? <PyramidOverview /> : <PyramidDetail />}
</React.Fragment>
);
};
export default Dashboard;
Overview component
import React, { useState, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
const Overview = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<main>
<div className="d-flex">
<button
onClick={() => setInfo({ uiname: "detail", pyramidvalue: 1 })}
>
change view
</button>
</div>
</main>
</div>
);
};
export default Overview;
Detail component
import React, { useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
// import axios from "axios";
const Detail = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<h2>Detail View</h2>
<div>
<button
type="button"
onClick={() => setInfo({ uiname: "overview", pyramidvalue: 0 })}
>
Back
</button>
</div>
</div>
);
};
export default Detail;
Context File
import React, { createContext, useEffect, useReducer } from "react";
let reducer = (info, newInfo) => {
return { ...info, ...newInfo };
};
const initialState = {
uiname: "overview",
pyramidvalue: 0,
};
const localState = JSON.parse(localStorage.getItem("pyramidcontent"));
const PyramidContext = createContext();
function PyramidProvider(props) {
const [info, setInfo] = useReducer(reducer, initialState || localState);
useEffect(() => {
localStorage.setItem("pyramidcontent", JSON.stringify(info));
}, [info]);
return (
<PyramidContext.Provider
value={{
info,
setInfo,
}}
>
{props.children}
</PyramidContext.Provider>
);
}
export { PyramidContext, PyramidProvider };
I click the button to render a detail view and soon as the page is refreshed, the component changes its view to overview instead of sticking around to detail. I checked the local storage values, and it is being updated properly, but still, the component view does not persist as per the value.
I am unable to understand where I am doing wrong, any help to resolve this issue, please? Thanks in advance.
You're never using the value of localStage in your info state,
you should replace your code with:
const [info, setInfo] = useReducer(reducer, localState ||Â initialState);
I'm building a quick authentication higher order component in Next.js and am getting some problems with the following code:
import SignIn from "../components/sign-in";
import { connect } from "react-redux";
import { useRouter } from "next/router";
const AuthenticationCheck = WrappedComponent => {
const { isAuthenticated, ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return !isAuthenticated && pageProtected ? (
<SignIn />
) : (
<WrappedComponent {...rest} />
);
};
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated
};
}
export default connect(mapStateToProps)(AuthenticationCheck);
If I change the code to remove redux & connect, it looks like this, and works perfectly.
const AuthenticationCheck = WrappedComponent => {
const { ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return pageProtected ? <SignIn /> : <WrappedComponent {...rest} />;
};
export default AuthenticationCheck;
I've been reading every SO, redux documentation etc for the last couple of hours, and I can't really find anything that matches what I'm doing, although I can't believe it's an uncommon use case.
Am I missing something obvious?
Solution: (Thankyou Dima for your help!)
So the final code that ended up working is:
import SignIn from "../components/sign-in";
import { connect } from "react-redux";
import { useRouter } from "next/router";
import { compose } from "redux";
const AuthenticationCheck = WrappedComponent => {
const authenticationCheck = props => {
const { isAuthenticated, ...rest } = props;
const router = useRouter();
const protectedPages = ["/colours", "/components"];
const pageProtected = protectedPages.includes(router.pathname);
return !isAuthenticated && pageProtected ? (
<SignIn />
) : (
<WrappedComponent {...rest} />
);
};
return authenticationCheck;
};
function mapStateToProps(state) {
return {
isAuthenticated: state.auth.isAuthenticated
};
}
export default compose(connect(mapStateToProps), AuthenticationCheck);
This works perfectly! 🙂
connect expects to get React component as a last argument, but you are sending HOC instead. You need to put connect and wrapper inside compose function. See below
import React from 'react'
import {compose} from 'redux'
import {connect} from 'react-redux'
import {doSomething} from './actions'
const wrapComponent = Component => {
const WrappedComponent = props => {
return (
<Component {...props} />
)
}
return WrappedComponent
}
const mapStateToProps = state => {
return {
prop: state.prop,
}
}
export default compose(
connect(mapStateToProps, {doSomething}),
wrapComponent
)
And the useit like this.
import React from 'react'
import withWrapper from 'your/path'
const Component = props => 'Component'
export default withWrapper(Component)