Deploying react app using fabricjs causing error in functionality - reactjs

I'm trying to deploy a React app using fabricjs. On localhost it's working fine but after deploying , fabric functions not working and causing an error
Uncaught TypeError: Cannot read properties of null (reading 'add')
what can be the issue ? and if anyone know it what can be the possible solution ?
here is the code for adding text box to the fabric canvas
import { fabric } from 'fabric';
import ContextCanvas from '../../../context/ContextCanvas';
import { Button } from '#chakra-ui/react';
const FabricTextBox = () => {
const [canvas] = useContext(ContextCanvas);
function addTextBox() {
const textbox = new fabric.Textbox('Click on the Rectangle to move it.', {
fontSize: 20,
left: 50,
top: 100,
width: 200,
fill: 'black',
color: 'white',
cornerColor: 'blue',
});
canvas.add(textbox);
canvas.requestRenderAll();
}
return (
<>
<Button
type="button"
colorScheme="blue"
onClick={addTextBox}
variant={'ghost'}
_hover={{}}
_focus={{}}
_active={{}}
textColor={'white'}
fontWeight={'light'}
>
Text Field
</Button>
</>
);
};
export default FabricTextBox;
here is my fabric canvas
import React, { useContext, useLayoutEffect } from 'react';
import { fabric } from 'fabric';
import ContextCanvas from '../../context/ContextCanvas';
const FabricCanvas = () => {
const [canvas, initCanvas] = useContext(ContextCanvas);
useLayoutEffect(() => {
return () => {
initCanvas(new fabric.Canvas('c'));
};
}, []);
return (
<>
<canvas
id="c"
width={window.innerWidth}
height={window.innerHeight}
/>
</>
)
}
export default FabricCanvas;
here is my context provider
import { fabric } from 'fabric';
const ContextCanvas = createContext();
export function CanvasProvider({ children }) {
const [canvas, setCanvas] = useState(null);
const initCanvas = c => {
setCanvas(c);
c.renderAll();
};
return (
<ContextCanvas.Provider value={[canvas, initCanvas]}>
{children}
</ContextCanvas.Provider>
);
}
export default ContextCanvas;

Related

Torch working on Android but not in iOS (ReactJS)

I'm building a QR scanner inside a ReactJS web app that is supposed to run on both Android and iOS. However, I cannot get the torch/flashlight to work on iOS.
I'm using the #blackbox-vision toolbox to handle both the torch and the QR scanner. As far as I understand you need to start the camera functionality and can use the video stream to manipulate the torch. Below code works fine on Android but not on iOS:
import { useState, useEffect, useRef } from "react";
import { QrReader } from "#blackbox-vision/react-qr-reader";
import { useTorchLight } from "#blackbox-vision/use-torch-light";
import styles from "./view.module.css";
import IconButton from "../../components/UI/iconbutton/view";
function SAQRView() {
const streamRef = useRef(null);
const [on, toggle] = useTorchLight(streamRef.current);
const [showTorchToggleButton, setShowTorchToggleButton] = useState(false);
const [msg, setMsg] = useState("");
const setRef = ({ stream }) => {
streamRef.current = stream;
setShowTorchToggleButton(true);
};
const previewStyle = {
width: "100%",
};
const onError = (error) => {
console.log(error);
};
const onTorchClick = (event) => {
toggle();
};
return (
<>
<div className={styles.container}>
<div className={styles.sub_container}>
<QrReader
delay={100}
showViewFinder={false}
style={previewStyle}
onLoad={setRef}
onError={onError}
onScan={setData}
constraints={{
facingMode: "environment",
video: true,
}}
/>
<div className={styles.footer}>
{showTorchToggleButton && (
<IconButton
icon="Flash_off"
toggleIcon="Flash_on"
isToggled={on}
onClick={onTorchClick}
/>
)}
</div>
{msg}
</div>
</div>
</>
);
}
export default SAQRView;
So then I tried manipulating the video stream manually:
import { useState, useEffect, useRef } from "react";
import { QrReader } from "#blackbox-vision/react-qr-reader";
import { useTorchLight } from "#blackbox-vision/use-torch-light";
import styles from "./view.module.css";
import IconButton from "../../components/UI/iconbutton/view";
function SAQRView() {
const streamRef = useRef(null);
const [on, toggle] = useTorchLight(streamRef.current);
const [showTorchToggleButton, setShowTorchToggleButton] = useState(false);
const [msg, setMsg] = useState("");
const setRef = ({ stream }) => {
streamRef.current = stream;
setShowTorchToggleButton(true);
};
const previewStyle = {
width: "100%",
};
const onError = (error) => {
console.log(error);
};
const onTorchClick = (event) => {
const tracks = streamRef.current.getVideoTracks();
const track = tracks[0];
setMsg(JSON.stringify(track.getCapabilities(), null, 2));
try {
if (!track.getCapabilities().torch) {
alert("No torch available.");
}
track.applyConstraints({
advanced: [
{
torch: true,
},
],
});
} catch (error) {
alert(error);
}
};
return (
<>
<div className={styles.container}>
<div className={styles.sub_container}>
<QrReader
delay={100}
showViewFinder={false}
style={previewStyle}
onLoad={setRef}
onError={onError}
onScan={setData}
constraints={{
facingMode: "environment",
video: true,
}}
/>
<div className={styles.footer}>
{showTorchToggleButton && (
<IconButton
icon="Flash_off"
toggleIcon="Flash_on"
isToggled={on}
onClick={onTorchClick}
/>
)}
</div>
{msg}
</div>
</div>
</>
);
}
export default SAQRView;
Again, this works on Android, but not iOS. Notice that I stringify the track capabilities and print them at the bottom of the screen. For Android this looks as follows:
And for iOS, it looks like this:
So it seems that iOS cannot access the torch capability. However, the torch will be greyed out when the QR scanner is active, so it does seem to grab hold of the torch.
Also we have tried installing the Chrome web browser but this gave exactly the same result.
Can I get around this and if so, how?

React + fabric.js

I am trying to combine react and fabricjs but I am stuck.
Here is my code
import React, { useState, useEffect, useRef } from 'react';
import { fabric } from "fabric";
function App() {
const [canvas, setCanvas] = useState('');
useEffect(() => {
setCanvas(initCanvas());
}, []);
const initCanvas = () => (
new fabric.Canvas('canvas', {
height: 800,
width: 800,
backgroundColor: 'pink' ,
selection: false,
renderOnAddRemove: true,
})
)
canvas.on("mouse:over", ()=>{
console.log('hello')
})
return (
<div >
<canvas id="canvas" />
</div>
);
}
export default App;
The problem is canvas.on as it causes the error 'Uncaught TypeError: canvas.on is not a function'
Please tell me what am I doing wrong here
During the initial render, your canvas variable is set to your initial state, '' from useState(''). It's not until after this that your useEffect will run, updating the state value.
Recommendation: Move your event handlers into the useEffect and use a ref instead of state for your canvas value. refs have the property of being directly mutable and not requiring a rerender for their new value to be available.
import React, { useState, useEffect, useRef } from 'react';
import { fabric } from "fabric";
function App() {
const canvas = useRef(null);
useEffect(() => {
canvas.current = initCanvas();
canvas.current.on("mouse:over", () => {
console.log('hello')
});
// destroy fabric on unmount
return () => {
canvas.current.dispose();
canvas.current = null;
};
}, []);
const initCanvas = () => (
new fabric.Canvas('canvas', {
height: 800,
width: 800,
backgroundColor: 'pink' ,
selection: false,
renderOnAddRemove: true,
})
);
return (
<div >
<canvas ref={canvas} />
</div>
);
}
export default App;
It's worth noting that if you don't need a reference to the canvas elsewhere in your component, you don't need to use state or a ref and can use a local variable within the useEffect.
useEffect(() => {
const canvas = initCanvas();
canvas.on("mouse:over", () => {
console.log('hello')
});
// destroy fabric on unmount
return () => {
canvas.dispose();
};
})
Actually the problem is that you trying to call canvas.on when it is an empty string in canvas (initial state)
Since we are only need to create fabric.Canvas once, I would recommend to store instance with React.useRef
I created an example for you here:
--> https://codesandbox.io/s/late-cloud-ed5r6q?file=/src/FabricExample.js
Will also show the source of the example component here:
import React from "react";
import { fabric } from "fabric";
const FabricExample = () => {
const fabricRef = React.useRef(null);
const canvasRef = React.useRef(null);
React.useEffect(() => {
const initFabric = () => {
fabricRef.current = new fabric.Canvas(canvasRef.current);
};
const addRectangle = () => {
const rect = new fabric.Rect({
top: 50,
left: 50,
width: 50,
height: 50,
fill: "red"
});
fabricRef.current.add(rect);
};
const disposeFabric = () => {
fabricRef.current.dispose();
};
initFabric();
addRectangle();
return () => {
disposeFabric();
};
}, []);
return <canvas ref={canvasRef} />;
};
export default FabricExample;

Nothing was returned from render in react functional component

I am working with react to fetch data from the node backend and implement the UI with the data. I rendered the UI conditionally but I do get an error in the console saying that nothing was returned from render. Here is my code
import React, { useEffect, useState } from "react";
import OT from "#opentok/client";
import { OTSession, OTPublisher, OTStreams, getPublisher } from "opentok-react";
import Connection from "./Connection";
import Publisher from "./Publisher";
import Subscriber from "./Subscriber";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { Creators } from "../../../services/redux/event/actions";
import { PropTypes } from "prop-types";
import { makeStyles, Container } from "#material-ui/core";
function Host(props) {
const [connect, setConnect] = useState(false);
const params = useParams();
const { event, error, isCreatingEvent } = props;
console.log(event, "event");
const handleSessionOn = () => {
setConnect(true);
};
useEffect(() => {
props.getSingle(params.id);
}, []);
if (isCreatingEvent) {
return <div>Loading .....</div>;
}
if (error) {
return <div>Error: {error.error_message}</div>;
}
if (event.sessionId != undefined) {
const { API_KEY: apiKey, sessionId, token } = event;
console.log(apiKey, sessionId, token)
return (
<div style={{ zIndex: 100 }}>
<Connection connect={connect} />
<h3 style={{ color: "red" }}>This is apiKey connect</h3>
<OTSession
sessionId={sessionId}
token={token}
apiKey={apiKey}
onConnect={handleSessionOn}
>
<Publisher />
<OTStreams>
<Subscriber sessionId={sessionId} />
</OTStreams>
</OTSession>
</div>
)
}
}
Host.protoTypes = {
event: PropTypes.object.isRequired,
error: PropTypes.string,
};
const mapDispatchToProps = (dispatch) => {
return {
getSingle: (id) => {
dispatch(Creators.getOneEvent(id));
},
};
};
const mapStateToProps = (state) => (
console.log(state),
{
event: state.event.event,
error: state.event.error,
isCreatingEvent: state.event.isCreatingEvent,
}
);
export default connect(mapStateToProps, mapDispatchToProps)(Host);
Can anyone please help me out? I used the redux state to connect with Vonage API but the OTSession is not being rendered.
You called the return function only on the if statement.
You should call the return function on the else statement.
Like this.
const { API_KEY: apiKey, sessionId, token } = event;
{event.sessionId != undefined ? (
<div style={{ zIndex: 100 }}>
<Connection connect={connect} />
<h3 style={{ color: "red" }}>This is apiKey connect</h3>
<OTSession
sessionId={sessionId}
token={token}
apiKey={apiKey}
onConnect={handleSessionOn}
>
<Publisher />
<OTStreams>
<Subscriber sessionId={sessionId} />
</OTStreams>
</OTSession>
</div>
) : null}

React useToggle component returning TypeError: Object is not a function or its return value is not iterable

I'm attempting a component which is essentially a "like" button that should be toggled on and off, using React and useToggle, however it's returning the TypeError: Object is not a function or its return value is not iterable.
import React, { useToggle } from 'react';
import ThumbUpIcon from '#material-ui/icons/ThumbUp';
const ThumbUpButton = {
backgroundColor: 'green',
border: 'none',
padding: '5px',
borderRadius: '5px',
}
const ThumbUp = {
color: 'white'
}
const Liker = () => {
const [like, unLike] = useToggle();
return (
<div>
<button style={ThumbUpButton} onClick={unLike}>
{like ? <ThumbUpIcon style={ThumbUp}/> : <ThumbUpIcon />}
</button>
</div>
);
}
export default Liker;
useToggle is not a hook exported from react. Have you written it in another file and messed uo the imports, perhaps? Cause this hook is not a React basic hook
You should define the hook 'useToogle` somewhere, then use it inside the 'Liker' component.
import React, { useState } from 'react';
const ToogleHook = (initValue) => {
const [value, setValue] = useState(initValue);
function toogleValue() {
setValue(!value);
}
return [value, toogleValue];
}
const Liker = () => {
const [like, toogleLike] = ToogleHook(false);
return <button onClick={toogleLike}>{like ? '👍🏿' : '👎🏿'}</button>
}
export default Liker;

Global screen loader in react

I am looking for a solution for using a global screen loader in react.
I am not that much familiar to react context, but I was wondering if that could help me here.
Basically I am introducing a screenloader and I was thinking that maybe the best way would be to have a global loader somewhere in main component.So to conclude:
I want to have global loader in main component
I want to update the state of global loader wherever I want in app
I don't want to pollute all the components with ScreenLoaders where I need to use it
I want to use hooks for it
So is there a way to have a global state of loader/loaderText and setting and resetting whenever needed using context?
If there is a simple way to do it, then do you think there might be any drawbacks of using such solution? Maybe that's an overkill for it.
What about creating a custom hook, useLoading, which can be used in any component that gives access to loading and setLoading from global context?
// LoadingContext.js
import { createContext, useContext, useState } from "react";
const LoadingContext = createContext({
loading: false,
setLoading: null,
});
export function LoadingProvider({ children }) {
const [loading, setLoading] = useState(false);
const value = { loading, setLoading };
return (
<LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
);
}
export function useLoading() {
const context = useContext(LoadingContext);
if (!context) {
throw new Error("useLoading must be used within LoadingProvider");
}
return context;
}
// App.jsx
import { LoadingProvider } from "./LoadingContext";
function App() {
return (
<LoadingProvider>
<RestOfYourApp />
</LoadingProvider>
);
}
// RestOfYourApp.js
import { useLoading } from "./LoadingContext";
function RestOfYourApp() {
const { loading, setLoading } = useLoading();
return (
{loading && <LoadingComponent />}
...
);
}
useLoader.js (hook)
import React, { useState } from "react";
import Loader from "./loader";
const useLoader = () => {
const [loading, setLoading] = useState(false);
return [
loading ? <Loader /> : null,
() => setLoading(true),
() => setLoading(false),
];
};
export default useLoader;
loader.js (loader componenet)
import React from "react";
import styled from "styled-components";
import spinner from "./loader.gif"; // create gif from https://loading.io
import Color from "../../Constant/Color";
const Loader = () => {
return (
<LoaderContainer>
<LoaderImg src={spinner} />
</LoaderContainer>
);
};
const LoaderContainer = styled.div`
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
background: ${Color.greyBg};
z-index: 100;
`;
const LoaderImg = styled.img`
position: absolute;
`;
export default Loader;
Using Loader hook
import useLoader from "../../../hooks/loader/useLoader"; /// import loader hook
const App = (props) => {
const [loader, showLoader, hideLoader] = useLoader(); //initialize useLoader hook
useEffect(() => {
showLoader(); /// loading starts
Axios.post("url")
.then((res) => {
hideLoader(); // loading stops
})
.catch((error) => {
hideLoader();// loading stops
});
}, []);
return (
<>
{loader} /// important
//// add your elements /////
</>
)
}
export default App;
Some more easy way
Create Provider with context and hook within single file
import React, {useRef} from 'react';
import {Loader} from '#components';
const LoaderContext = React.createContext();
export function LoaderProvider({children}) {
const ref = useRef();
const startLoader = () => ref.current.start();
const stopLoader = () => ref.current.stop();
const value = React.useMemo(
() => ({ref, startLoader, stopLoader}),
[ref, startLoader, stopLoader]
);
return (
<LoaderContext.Provider value={value}>
{children}
<Loader ref={ref} />
</LoaderContext.Provider>
);
}
export const useLoader = () => React.useContext(LoaderContext);
in App.js add provider
import {StoreProvider} from 'easy-peasy';
import React from 'react';
import {StatusBar, View} from 'react-native';
import colors from './src/assets/colors';
import Navigation from './src/navigation/routes';
import {LoaderProvider} from './src/providers/LoaderProvider';
import {ToastProvider} from './src/providers/ToastProvider';
import store from './src/redux/store';
import globalStyles from './src/styles/index';
import('./src/helpers/ReactotronConfig');
function App() {
return (
<StoreProvider store={store}>
<StatusBar
barStyle="light-content"
backgroundColor={colors.backgroundDark}
translucent={false}
/>
<ToastProvider>
<LoaderProvider>
<View style={globalStyles.flex}>
<Navigation />
</View>
</LoaderProvider>
</ToastProvider>
</StoreProvider>
);
}
export default App;
And in any screen use like this way
import {useLoader} from '../../providers/LoaderProvider';
const {startLoader, stopLoader} = useLoader();
Loader.js
import React, {forwardRef, useImperativeHandle, useState} from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {wp} from '../../styles/responsive';
function Loader(props, ref) {
const [loading, setLoading] = useState(0);
useImperativeHandle(
ref,
() => ({
start: () => {
const loadingCount = loading + 1;
setLoading(loadingCount);
},
stop: () => {
const loadingCount = loading > 0 ? loading - 1 : 0;
setLoading(loadingCount);
},
isLoading: () => loading >= 1,
}),
[],
);
if (!loading) {
return null;
}
return (
<View style={styles.container}>
<ActivityIndicator size={'small'} color={'#f0f'} />
</View>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFill,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#11111150',
zIndex: 999,
elevation: 999,
},
});
export default forwardRef(Loader);
You can use this package for simple react loading : https://www.npmjs.com/package/react-global-loading
Usage :
import { GlobalLoading, showLoading } from 'react-hot-toast';
const App = () => {
const show = () => {
showLoading(true);
setTimeout(() => {
showLoading(false);
}, 1000);
};
return (
<div>
<button onClick={show}>Show Loading</button>
<GlobalLoading />
</div>
);
};

Resources