Why useContext doesn't re-render the component - React.js + Laravel - javascript

I'm stucked... :)
I have a single view in Laravel where React appends to the specific ID. What I try to do is to open the pop-up after click the button using an useContext. Below is my code:
Globalcontext.js
import React from 'react';
export const initialState = {
usersData: null,
clickedNewUserButton: false,
showAddUserPopup: false,
isLoading: true,
};
export const GlobalContext = React.createContext(initialState);
UsersPageMain.js
import React, { useEffect, useContext } from 'react';
import ReactDOM from 'react-dom';
import { GlobalContext } from '../state/GlobalContext';
import FiltersButton from '../components/Users/FiltersButton';
import AddUserButton from '../components/Users/AddUserButton';
import UsersTable from '../components/Users/UsersTable';
import AddNewUserPopup from '../components/Users/AddNewUserPopup';
function UsersPageMain(){
const initialState = useContext(GlobalContext);
if(initialState.clickedNewUserButton){
return (
<GlobalContext.Provider value={initialState}>
<div className='container users-list-page'>
<div className='row'>
<FiltersButton/>
<AddUserButton/>
</div>
<div className='row'>
<UsersTable></UsersTable>
</div>
</div>
<AddNewUserPopup/>
</GlobalContext.Provider>
)
}else{
return (
<GlobalContext.Provider value={initialState}>
<div className='container users-list-page'>
<div className='row'>
<FiltersButton/>
<AddUserButton/>
</div>
<div className='row'>
<UsersTable></UsersTable>
</div>
</div>
</GlobalContext.Provider>
)
}
}
export default UsersPageMain;
if (document.getElementById('user-list-page')) {
ReactDOM.render(<UsersPageMain />, document.getElementById('user-list-page'));
}
UsersTable.js
import axios from 'axios';
import React, {useContext,useEffect,useState} from "react";
import { GlobalContext } from "../../state/GlobalContext";
import Preloader from '../Preloader';
import Conf from '../../conf/Conf';
export default function UsersTable(){
const context = useContext(GlobalContext);
const [loading, setLoading] = useState(true);
useEffect(() => {
axios.get('/api/get-all-users')
.then(response => {
context.usersData = response.data.data;
setLoading(false);
})
})
if(loading){
return (
<Preloader isLoading={loading}/>
)
}else{
return (
<>
<Preloader isLoading={loading}/>
<div className="col-12">
<div className="table-responsive rounded-table">
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>Avatar</th>
<th>Name</th>
<th>Surname</th>
<th>Group</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{context.usersData.map((user,index) => {
return (
<tr key={user.email}>
<th>{user.id}</th>
<th>
<div className="avatar" style={{backgroundImage: `url(${Conf().assetPath}uploads/avatars/${user.avatar})`}}></div>
</th>
<th>{user.name}</th>
<th>{user.surname}</th>
<th>Group</th>
<th>{user.email}</th>
<th>
<button type="button" className="btn theme-edit-btn"><i className="fa-solid fa-pencil"></i></button>
<button type="button" className="btn theme-delete-btn"><i className="fa-solid fa-delete-left"></i></button>
</th>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
</>
)
}
}
AddUserButton.js
import React, { useContext } from "react";
import { GlobalContext } from "../../state/GlobalContext";
export default function AddUserButton(){
const context = useContext(GlobalContext);
function triggerUserPopupClick(){
context.clickedNewUserButton = true
}
return(
<div className="col-xl-2 col-md-4 col-12">
<button type="button" onClick={() => {triggerUserPopupClick()}} className="btn theme-primary-btn">Add New <i className="fa-solid fa-angles-right"></i></button>
</div>
)
}
The problem is exactly in the AddUserButton component where I'm trying to update global context to re-render main UsersPageMain component. I don't have any idea what I'm doing wrong... If you could, please give some tips what I need to do to achieve opening popup after clicking this component.
Here's the working snippet on the codesanbox, might it will be helpfull.
https://codesandbox.io/embed/adoring-bell-dv3tfc?fontsize=14&hidenavigation=1&theme=dark

function triggerUserPopupClick(){
context.clickedNewUserButton = true
}
React has only one1 way to cause a rerender: setting state. React can't tell that you mutated the context object here, so it will not rerender.
To make this work, you will need to set up your top level component to have a state variable. It will then make both the state and the setter function available via context.
// In globalcontext.js
export const initialState = {
usersData: null,
clickedNewUserButton: false,
showAddUserPopup: false,
isLoading: true,
};
export const GlobalContext = React.createContext({
state: initialState,
setState: () => {},
});
// In UsersPageMain.js
function UsersPageMain(){
const [state, setState] = useState(initialState);
// It's important to memoize this so you don't make a new object unless
// the state has changed. Otherwise, every render of UsersPageMain will
// necessarily trigger a render of every consumer of the context.
const contextValue = useMemo(() => {
return {
state,
setState,
};
}, [state]);
if(state.clickedNewUserButton){
return (
<GlobalContext.Provider value={contextValue}>
// ...
</GlobalContext.Provider>
);
} else {
return (
<GlobalContext.Provider value={contextValue}>
// ...
</GlobalContext.Provider>
)
}
}
And then you'll call that setState function when you want to change it:
// In AddUserButtonjs
const { state, setState } = useContext(GlobalContext);
function triggerUserPopupClick(){
setState({
...state,
clickedNewUserButton: true
});
}
Don't forget to update any other places where you're using the context so that they expect to get an object with a .state property, instead of getting the state directly.
1) Ok, technically there's a second way: force update in class components. But don't use that

I figured out what was wrong. I wrote the async function and then into the useEffect I resolve the promise. The problem is that still I don't know why my axios was getting into the loop. Here's a working part of code :
import axios from 'axios';
import React, {useContext,useEffect,useState} from "react";
import { GlobalContext } from "../../state/GlobalContext";
import Preloader from '../Preloader';
import Conf from '../../conf/Conf';
async function fetchUsersData() {
const response = await axios.get('/api/get-all-users');
return response;
}
export default function UsersTable(){
const { state, setState } = useContext(GlobalContext);
useEffect( () => {
fetchUsersData().then(response => {
setState({
...state,
usersData: response.data.data,
isLoading: false,
})
})
}, [null]);
return (
<></>
)
}

Related

Error: Objects are not valid as a React child (found: [object Promise]).….. While getting data from supabase

I am having a problem while getting data from supabase .
Could any one help me
`
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const { data, error} = await supabase.from("Employees").select("*")
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
`
I tried using map, other function, and looked it up yet nothing works
The problem is how you are fetching the data. Try fetching your data inside an useEffect hook:
import Link from "next/link";
import { supabase } from "../../supabase";
import { useState, useEffect } from "react";
function Index() {
// const { data, error } = await supabase.from("Employees").select("*")
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
let cancelFetch = false; // to avoid race conditions on React18
supabase
.from("Employees")
.select("*")
.then((res) => {
if (!cancelFetch) {
setData(res.data);
setError(res.error);
}
});
return () => {
cancelFetch = true;
};
}, []);
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return <h1>{index.name}</h1>;
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
);
}
export default Index;
More info on fetching and useEffect here: https://beta.reactjs.org/apis/react/useEffect#fetching-data-with-effects
Your source code is invalid. React components should always be a function (or class) that returns a react object. it does not accept a promise that returns a react object.
You will probably want to use react's useEffect to solve this problem:
import { useState, useEffect } from "react";
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
supabase.from("Employees").select("*")
.then(data => setData(data))
.catch(err => setError(err))
}, [])
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
Your component cannot be async, because it returns a Promise and React doesn't like that.
There is a cool function on Next.js that allows you to fetch data asynchronously, try that:
function Index({ data }) {
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
export async function getServerSideProps() {
const { data, error} = await supabase.from("Employees").select("*")
return {
props: {
data: data
}
}
}
More here: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
Based on the way you are fetching data, I believe you are using next13 and you are in app directory. When you rendered jsx
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
index refers to each element inside the data array. Most likely index.name is an object. that is why it is throwing that error.
console.log("index name",index.name)
If you are using async functional component, you should be using Suspense api. Create a separeate component maybe async Users, fetch the data inside this component, and when you want to display the users inside the Index
import {Suspense} from "react"
function Index(){
return (
<>
....
<Suspense fallback={ <h1>Users are loading...</h1>} >
<Users/>
</Suspense>
....
</>
)
}
You only use async component inside app folder and server component.

How to get value of a component to another component

I am creating a Project where I need to get value of a component state value to another component.
How can I get the value?
Information
Both are functional component.
I need to send data from A (state) to B(state).
A state data are set from react dropdown menu.Which is a button.
I don't use Link or Route to A dropdown that's why I can't get it with useParams()
I need to set value in B component which will fetch data with passing language.
I have import all needed things & don't have any warnings & error.
Code of component A
Send value from this language state to B
const A = () => {
const [language, setLanguage] = useState('en');
return (
<Navbar expand="xl" bg='light' expand={false}>
<Container>
<DropdownButton id="dropdown-basic-button" title={<MdOutlineLanguage />}>
<Dropdown.Item as="button" onClick={() => setLanguage('en')}>English</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => setLanguage('ar')}>العربية</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => setLanguage('bn')}>বাংলা</Dropdown.Item>
</DropdownButton>
</Container>
</Navbar>
)
};
export default A
Code of Component B
I need to get here A state value & set it to B state. Then pass to useGetDataQuery & fetch data.
const B = () => {
let [language, setLanguage] = useState('en')
const { data } = useGetDataQuery({language })
return (
<>
</>
)
}
export default B
Redux Section
I'm using readux & #reduxjs/toolkit to store fetch data. Can I store my language data to here. Than how can get to from anywhere of my component.
react-rotuer-dom v6
export default configureStore({
reducer: {
[dataOne.reducerPath]: dataOne.reducer,
[data2.reducerPath]: dataTwo.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false
}).concat([dataOne.middleware, dataTwo.middleware]),
})
Maybe instead of using useState, you can use a global state by using useContext because I think your language will be use on several places as request body and of course to edit the state value, you can combine it with useReducer.
// UPDATE
working code: https://codesandbox.io/s/sleepy-monad-21wnc
MyContext
import { useReducer, useContext, createContext } from "react";
const initialValue = {
language: "id"
// other: "value",
};
const AppContext = createContext(initialValue);
const AppReducer = (state, action) => {
switch (action.type) {
case "CHANGE_LANGUAGE":
return {
...state,
...action.payload
};
default:
return state;
}
};
const MyContext = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialValue);
return (
<AppContext.Provider value={[state, dispatch]}>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default MyContext;
this is the context and reducer component later on use in App.js
App.js
import A from "./A";
import B from "./B";
import MyContext from "./MyContext";
import "./styles.css";
export default function App() {
return (
<MyContext>
<div className="App">
<A />
<B />
</div>
</MyContext>
);
}
A.js
import { useAppContext } from "./MyContext";
const A = () => {
const [globalState, dispatch] = useAppContext();
const onChange = (e) => {
dispatch({
type: "CHANGE_LANGUAGE",
payload: {
[e.target.name]: e.target.value
}
});
};
return (
<>
<p>Start Of Component A </p>
<input value={globalState.language} name="language" onChange={onChange} />
<p>End Of Component A </p>
</>
);
};
export default A;
B.js
import { useAppContext } from "./MyContext";
const B = () => {
const [globalState, dispatch] = useAppContext();
return (
<>
<p>Start Of Component B </p>
<h2>Language Val : {globalState.language}</h2>
<p>End Of Component B </p>
</>
);
};
export default B;

Unable to trigger function from context api with react hooks

I'm trying to trigger a function from my CartContext Api upon a click, but it isn't happening. I have checked the method and it works, but when I add the context function it doesn't do anything... see below code:
Context file
import React, { useState } from 'react';
export const CartContext = React.createContext({
cart: [],
setCart: () => {},
});
const CartContextProvider = (props) => {
const [updateCart, setUdatedCart] = useState();
const updateCartHandler = () => {
console.log('click');
};
return (
<CartContext.Provider
value={{ cart: updateCart, setCart: updateCartHandler }}
>
{props.children}
</CartContext.Provider>
);
};
export default CartContextProvider;
Component where Im using the context:
import React, { useContext } from 'react';
import classes from './SingleProduct.css';
import AddToCartBtn from './AddToCartBtn/AddtoCartBtn';
import { CartContext } from '../context/cart-context';
const singleProduct = (props) => {
const cartContext = useContext(CartContext);
const addToCart = (id, productName, price, qty) => {
const productInCart = {
productId: id,
productName: productName,
productPrice: price,
productQty: qty,
};
cartContext.setCart();
};
return (
<article className={classes.SingleProduct}>
<div className={classes.ProductImgContainer}>
<img src={props.productImg} alt="" />
</div>
<div className={classes.ProductTitle}>
<h2>{props.productName}</h2>
</div>
<AddToCartBtn
clicked={() => {
addToCart(
props.productId,
props.productName,
props.productPrice,
props.productQty
);
}}
/>
</article>
);
};
export default singleProduct;
I'm just adding a console.log('click') to check if the method triggers at the moment. By the way, when I console.log the context variable it contains the properties and works. Any ideas why this isn't happening
Forgot to wrap the component with provider thanks!

fetch data inside useEffect using action in redux

I am trying to fetch my data inside useEffect but every time i get an empty array when i try to send it as props to another component (Product)
ProducList.js
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { fetchProducts } from "../actions/products";
import { Product } from "./Product";
const ProductList = ({ getProducts, products, loading }) => {
useEffect(() => {
getProducts();
}, []);
return (
<div className="p-4">
<Product products={data} />
</div>
);
};
const mapStateToProps = state => ({
products: state.products,
loading: state.loading
});
const mapDispatchToProps = {
getProducts: fetchProducts
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
and here my Product.js
import React from "react";
export const Product = props => {
const products = props.products.map(product => {
return (
<div className="col-lg-4 mb-4" key={product.Id}>
<div className="card shadow-sm">
<img
className="card-img-top"
src={`/images/${product.Id}.jpg`}
alt={product.name}
/>
<div className="card-body">
<h5 className="card-title">
{product.name}{" "}
<span className="badge badge-warning">${product.price}</span>
</h5>
<a href="#" className="btn btn-secondary mx-auto">
Add to cart
</a>
</div>
</div>
</div>
);
});
return <div className="row">{products}</div>;
};
i need to fetch data and send to product component
It looks like your data is never passed into the ProductList component (there is no reference to a data prop in ProductList).
I believe you are trying to pass the products data into the Products component. Here's what you could do.
const [newProducts, setNewPropducts] = useState([]);
//add this useEffect and keep your other one
useEffect(() => {
//set state for products here
setProducts(products)
}, [products]);
<Product products={newProducts} />

React TypeScript button with loading spinner

I have a button inside a react component, the test on the button says Join Now when the button is clicked I call the function submitForm which then sets the state to true. I want the loading svg image to be displayed but instead the path to the image is shown on the button.
import React, { useState } from 'react';
import Context from './context';
import loading from '../images/loading.svg';
const ButtonSpinner: React.FC = () => {
const [state, setState] = useState({loading: false});
function submitForm() {
setState({ ...state, loading: true})
}
return (
<button className="btn-join" onClick={submitForm}>
{!state.loading && 'Join Now!'}
{state.loading && loading}
</button>
);
}
export {ButtonSpinner};
try displaying image like this instead- you need an img tag in order to display image content, React won't do that out of the box.
return (
<button className="btn-join" onClick={submitForm}>
{!state.loading && 'Join Now!'}
{state.loading && <img src={loading} /> }
</button>
);
import React, { useState } from 'react';
import Context from './context';
import loading from '../images/loading.svg';
const ButtonSpinner: React.FC = () => {
const [state, setState] = useState({loading: false});
function submitForm() {
setState({ ...state, loading: true})
}
return (
<button className="btn-join" onClick={submitForm}>
{!state.loading ? 'Join Now!' : <img src={loading} alt="loading" />}
</button>
);
}
export {ButtonSpinner};

Categories

Resources